Skip to main content

Writing efficient custom code in Web Experimentation

In the Kameleoon back office, there are multiple entry points where you can add custom JavaScript that executes on your website. This article provides specific recommendations for each available entry point. Please note that all Kameleoon scripts run once per page load.

Before implementing custom scripts, consider using Kameleoon Command Queue for optimized execution.

Kameleoon Command Queue

The most efficient way to interact with Kameleoon’s API—whether for converting goals, setting custom data, or triggering events—is to leverage existing triggers in your Tag Manager (e.g., Google Tag Manager) alongside the Kameleoon Command Queue. This should be your primary approach when adding JavaScript to Kameleoon. Instead of writing lengthy JavaScript code directly in Kameleoon, you can achieve the same results with just two lines of code within your Tag Manager.

Examples

  • Convert a revenue goal

In your Tag Manager, create a trigger on the confirmation page to capture the revenue, and add the following code:

// Replace kameleoonGoalID and {{revenue}} with your actual values
window.kameleoonQueue = window.kameleoonQueue || [];
window.kameleoonQueue.push(['Goals.processConversion', kameleoonGoalID, {{revenue}}])

For guidance on creating a goal conversion from GTM, see this documentation.

  • Set a Custom Data value when the visitor logs in

Instead of writing custom JS in Kameleoon, set the value directly via your Tag Manager:

// Replace "customDataName" and "customDataValue" with your actual values
window.kameleoonQueue = window.kameleoonQueue || [];
window.kameleoonQueue.push(['Data.setCustomData', "customDataName", "customDataName"]);
  • Trigger an event when a product is added to the cart

Use this to target visitors based on specific actions:

// Replace "customEventName" with your actual event name
window.kameleoonQueue = window.kameleoonQueue || [];
window.kameleoonQueue.push(['Events.trigger', "customEventName"]);

If kameleoonQueue cannot be utilized, please refer to the specific recommendations for each available script detailed below.

Global Script & variation scripts

Optimize JavaScript execution with the Activation API

Utilize Kameleoon’s Activation API to apply variations at the appropriate time. This approach helps prevent performance issues and ensures a seamless experience for visitors.

Below are some essential functions of the Activation API that can be used to enhance visitor experience:

  • runWhenConditionTrue : Executes the code when a specific condition is met. By default, it runs every 200 milliseconds. However, be careful with conditions that may never resolve, as this can lead to unnecessary looping. Note that the loop will continue running until the function explicitly returns true. Returning false or undefined will not stop the loop or trigger the callback.

❌ Incorrect usage (Infinite loop risk): The following example will never return true on all pages other than the product page, causing the loop to run indefinitely:

Kameleoon.API.Core.runWhenConditionTrue(() => {
return window.dataLayer && window.dataLayer.find(layer => layer.pageType === "product");
}, () => {
// Product page logic here
});

✅ Recommended approach: Always return true to exit the loop, and then handle the logic inside the callback:

let pageTypeLayer;
Kameleoon.API.Core.runWhenConditionTrue(() => {
pageTypeLayer = window.dataLayer && window.dataLayer.find(layer => layer.pageType);
return pageTypeLayer;
}, () => {
if (pageTypeLayer?.pageType === "product") {
// Product page logic here
}
});
note

It is preferable to retrieve information using the URL or cookies/localStorage instead of depending on runWhenConditionTrue to wait for the dataLayer or other late-loading objects, like when trying to get information about the page type. This approach ensures faster execution and better performance.

  • runWhenElementPresent: This method ensures that your code runs only when a specific element is present in the DOM, avoiding unnecessary delays caused by dynamically loaded elements.

Key considerations

  • Uses Mutation Observers by default (recommended for performance).
  • If Mutation Observers are disabled on your site, you can specify a polling interval as the 3rd argument of this method.
  • If the element is dynamically inserted into the DOM, set isDynamicElement to true as the 4th argument of this method:
caution

Use the code below sparingly and only when absolutely necessary. Limit its usage to the minimum number of cases and avoid applying it to multiple elements simultaneously to maintain optimal page performance.

Be mindful of the potential for an infinite loop if the source code of the page modifies the element each time it is updated.

Kameleoon.API.Core.runWhenElementPresent('#myDynamicElement', ([elem]) => {
// Logic once the element loaded
}, null, true); // isDynamicElement is set to true

Set your code's scope to specific pages

To optimize performance, make sure your code executes only on the relevant pages. This is particularly important when using loop-based methods like runWhenConditionTrue and runWhenElementPresent, as running these methods across the entire site can lead to unnecessary executions and impact performance. For instance, if you are waiting for revenue information in the dataLayer, wrap your code in a condition that checks whether the URL corresponds to the confirmation page. This approach ensures that the code runs only on the confirmation page instead of being triggered site-wide.

Example: Targeting a specific confirmation page before running a loop

if (document.location.href.includes('/confirmation')) {
let revenueLayer;
Kameleoon.API.Core.runWhenConditionTrue(() => {
revenueLayer = window.dataLayer && window.dataLayer.find(layer => layer.revenue);
return revenueLayer;
}, () => {
Kameleoon.API.Goals.processConversion(goalId, revenueLayer.revenue);
});
}

Similarly, if you're waiting for an element to be loaded on the page, make sure the element is expected to be displayed at some point and only execute the script on the relevant page.

if (document.location.href.includes('/subscription')) {
Kameleoon.API.Core.runWhenElementPresent("#form", () => {
// Add the rest of your logic here
});
}

For variation scripts, prefer CSS over JavaScript for visual changes

Using CSS instead of JavaScript results in faster and smoother rendering. CSS can be utilized for hiding or modifying elements, swapping blocks, and changing styles or text.

Handling Single Page Applications (SPAs)

If your experiment is running on a single-page application (SPA) or a dynamically updating page, specific implementation steps are required. Unlike traditional websites, SPAs do not reload entire pages, so Kameleoon needs to identify when content updates happen. Please refer to our guide on setting up an experiment on a single-page application for best practices.

Segments / Triggers

The first best practice is to base your targeting on data that is immediately available when the page loads. This includes information such as the page URL, browser type, device type, and any data stored in cookies or local storage.

If your targeting requires data that is not instantly accessible, follow these guidelines to ensure efficient execution and avoid unnecessary delays.

Custom Javascript Condition

This condition allows you to use custom JavaScript to target visitors. There are two main options available:

Check condition immediately

This script executes every 75 milliseconds before the DOM is fully loaded, and then every 250 milliseconds afterward. By default, it returns undefined, which causes the script to continue running in a loop until it explicitly returns true (to include the visitor) or false (to exclude them). Always ensure that your condition eventually resolves to either true or false to prevent unnecessary looping. Here’s an optimized example:

if (window.dataLayer && window.dataLayer.some(layer => layer.pageType == "homepage")) return true; 

else if (window.dataLayer && window.dataLayer.some(layer => layer.pageType != "homepage")) return false;

Run the condition asynchronously

Use this script when you need to wait for a response from an external web service or API before targeting the visitor. Once you receive the response, call setTargeting(true) or setTargeting(false). See the code below.

note
  • If you want to target a specific element, use the native condition “Element on the page” instead.
  • If you want to wait for information on the page, use the “check condition immediately” option above.
const userId = localStorage.getItem("user_id"); 
if (!userId) {
setTargeting(false);
}
// Example API endpoint - replace with your actual endpoint
const apiUrl = `https://api.example.com/check-segment?userId=${userId}`;

fetch(apiUrl, { method: "GET", headers: { "Content-Type": "application/json" } })
.then(response => response.json())
.then(data => {
if (data && data.isInSegment) {
setTargeting(true);
} else {
setTargeting(false);
}
})
.catch(error => {
setTargeting(false);
});

Custom event

Using custom events is an effective and passive way to target visitors. Rather than continuously monitoring conditions, this method listens for a specific custom event that you define and triggers targeting when that event occurs. This approach is particularly useful in two key scenarios:

When the targeting logic is complex

It’s better to avoid embedding all the logic directly in a JavaScript condition within the targeting. Instead, you can define a custom event in the Global Script. For example, if you want to target visitors on the cart page who have at least three products in their cart and a total amount above $30, you would trigger an event in the Global Script once these conditions are met, rather than writing all of the logic within the JavaScript segment condition.

When multiple segments share similar logic

If several segments require the same or slightly different variations of the same condition, using a custom event prevents redundant code and enhances maintainability.

Example: You need to target visitors based on different cart amounts. Instead of duplicating the logic in multiple segments, you define a single script in the Global Script and trigger different custom events for each segment:

if (document.location.href.includes('/cart')) {
Kameleoon.API.Core.runWhenElementPresent("#cartContainer", ([cartContainer]) => {
if (cartContainer.length >= 3) {
const totalAmount = Number(cartContainer.querySelector('#totalAmount').innerText);

if (totalAmount >= 90)
Kameleoon.API.Events.trigger("3ArticlesAbove90"); // Segment 1
else if (totalAmount >= 60)
Kameleoon.API.Events.trigger("3ArticlesAbove60"); // Segment 2
else if (totalAmount >= 30)
Kameleoon.API.Events.trigger("3ArticlesAbove30"); // Segment 3
}
});
}

Custom Data

Custom data, like custom events, helps to avoid duplicate code by allowing you to store and reuse values. You can define custom data in the Global Script or in other scripts, and then utilize it within segments. However, custom data differs from custom events in two key ways:

Scope Options: "Page", "Visit", or "Visitor"

Unlike custom events, which only apply to the current page, custom data can persist across multiple pages and sessions.

  • Visit Scope: This keeps the value throughout a visitor's session.
  • Visitor Scope: This stores the value for up to 365 days, depending on the cookie policy.

For example, if you want to display a banner on all pages once a visitor logs in, you can set a custom data value to "true" with a visit scope. This ensures that the visitor remains targeted with the banner across all pages during their session after logging in.

Enhanced Experiment Insights

Custom data can serve as filters and breakdown criteria on the results page of experiments. This enables deeper insights and allows for more effective analysis of visitor behavior.

To discover all available targeting conditions in the segment builder, please refer to this documentation.